﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;

//////////////////////
////Source: http://subversion.assembla.com/svn/outcoldman_p 
/////////////////////
namespace FU.Capstones.IMS.Web.Common.InventoryServiceProxy.DataContracts
{
    [DataContract]
	public abstract class BindingModelBase<TBindingModel> : INotifyPropertyChanged, INotifyDataErrorInfo
		where TBindingModel : BindingModelBase<TBindingModel>
	{
		private readonly List<PropertyValidation<TBindingModel>> validations;
		private Dictionary<string, List<string>> errorMessages;

		protected BindingModelBase()
        {
            validations = new List<PropertyValidation<TBindingModel>>();
            errorMessages = new Dictionary<string, List<string>>();
			PropertyChanged += (s, e) => { if (e.PropertyName != "HasErrors") ValidateProperty(e.PropertyName); };
		}

		#region INotifyDataErrorInfo

		public IEnumerable GetErrors(string propertyName)
		{
			if (errorMessages !=null && errorMessages.ContainsKey(propertyName)) 
				return errorMessages[propertyName];

			return new string[0];
		}

		public bool HasErrors
		{
            get { return errorMessages != null && errorMessages.Count > 0; }
		}

		public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };

		private void OnErrorsChanged(string propertyName)
		{
			ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
		}

		#endregion

		#region INotifyPropertyChanged

		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChanged(string propertyName)
		{
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
		}

		#endregion

		protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
		{
			RaisePropertyChanged(GetPropertyName(expression));
		}
        
		protected PropertyValidation<TBindingModel> AddValidationFor<T>(Expression<Func<T>> expression)
		{
			return AddValidationFor(GetPropertyName(expression));
		}

		protected PropertyValidation<TBindingModel> AddValidationFor(string propertyName)
		{
			var validation = new PropertyValidation<TBindingModel>(propertyName);
			validations.Add(validation);

			return validation;
		}

		protected void AddAllAttributeValidators()
		{
			PropertyInfo[] propertyInfos = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

			foreach (PropertyInfo propertyInfo in propertyInfos)
			{
				Attribute[] custom = Attribute.GetCustomAttributes(propertyInfo, typeof(ValidationAttribute), true);
				foreach (var attribute in custom)
				{
					var property = propertyInfo;
					var validationAttribute = attribute as ValidationAttribute;

					if (validationAttribute == null)
						throw new NotSupportedException("validationAttribute variable should be inherited from ValidationAttribute type");

					string name = property.Name;

					var displayAttribute = Attribute.GetCustomAttributes(propertyInfo, typeof(DisplayAttribute)).FirstOrDefault() as DisplayAttribute;
					if (displayAttribute != null)
					{
						name = displayAttribute.GetName();
					}

					var message = validationAttribute.FormatErrorMessage(name);

					AddValidationFor(propertyInfo.Name)
						.When(x =>
						{
							var value = property.GetGetMethod().Invoke(this, new object[] { });
							var result = validationAttribute.GetValidationResult(value,
																	new ValidationContext(this, null, null) { MemberName = property.Name });
							return result != ValidationResult.Success;
						})
						.Show(message);

				}
			}
		}

		public void ValidateAll()
		{
			var propertyNamesWithValidationErrors = errorMessages.Keys;

			errorMessages = new Dictionary<string, List<string>>();

			validations.ForEach(PerformValidation);

			var propertyNamesThatMightHaveChangedValidation =
				errorMessages.Keys.Union(propertyNamesWithValidationErrors).ToList();

			propertyNamesThatMightHaveChangedValidation.ForEach(OnErrorsChanged);

			RaisePropertyChanged(() => HasErrors);
		}

		public void ValidateProperty<T>(Expression<Func<T>> expression)
		{
			ValidateProperty(GetPropertyName(expression));
		}

		private void ValidateProperty(string propertyName)
		{
			errorMessages.Remove(propertyName);

			validations.Where(v => v.PropertyName == propertyName).ToList().ForEach(PerformValidation);
			OnErrorsChanged(propertyName);
			RaisePropertyChanged(() => HasErrors);
		}

		private void PerformValidation(PropertyValidation<TBindingModel> validation)
		{
			if (validation.IsInvalid((TBindingModel) this))
			{
				AddErrorMessageForProperty(validation.PropertyName, validation.ErrorMessage);
			}
		}

		private void AddErrorMessageForProperty(string propertyName, string errorMessage)
		{
			if (errorMessages.ContainsKey(propertyName))
			{
				errorMessages[propertyName].Add(errorMessage);
			}
			else
			{
				errorMessages.Add(propertyName, new List<string> {errorMessage});
			}
		}

		private static string GetPropertyName<T>(Expression<Func<T>> expression)
		{
			if (expression == null)
				throw new ArgumentNullException("expression");

			MemberExpression memberExpression;

			if (expression.Body is UnaryExpression)
				memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression;
			else
				memberExpression = expression.Body as MemberExpression;

			if (memberExpression == null)
				throw new ArgumentException("The expression is not a member access expression", "expression");

			var property = memberExpression.Member as PropertyInfo;
			if (property == null)
				throw new ArgumentException("The member access expression does not access a property", "expression");

			var getMethod = property.GetGetMethod(true);
			if (getMethod.IsStatic)
				throw new ArgumentException("The referenced property is a static property", "expression");

			return memberExpression.Member.Name;
		}
	}
}
